package com.fary.coyote;

import com.fary.net.AbstractEndpoint;
import com.fary.net.AbstractEndpoint.Handler;
import com.fary.net.SocketEvent;
import com.fary.net.SocketWrapperBase;

import javax.management.ObjectName;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

public abstract class AbstractProtocol<S> implements ProtocolHandler {

    private final AbstractEndpoint<S> endpoint;

    private final Set<Processor> waitingProcessors = Collections.newSetFromMap(new ConcurrentHashMap<>());

    private AsyncTimeout asyncTimeout = null;

    private Handler<S> handler;

    protected Adapter adapter;

    public AbstractProtocol(AbstractEndpoint<S> endpoint) {
        this.endpoint = endpoint;
    }

    protected AbstractEndpoint<S> getEndpoint() {
        return endpoint;
    }

    protected abstract String getNamePrefix();

    public String getName() {
        return ObjectName.quote(getNameInternal());
    }

    public int getPort() {
        return endpoint.getPort();
    }


    private String getNameInternal() {
        return getNamePrefix() + '-' + getPort();
    }

    public void setConnectionTimeout(int timeout) {
        endpoint.getSocketProperties().setSoTimeout(timeout);
    }

    protected void setHandler(Handler<S> handler) {
        this.handler = handler;
    }

    @Override
    public void setAdapter(Adapter adapter) {
        this.adapter = adapter;
    }

    @Override
    public Adapter getAdapter() {
        return adapter;
    }

    @Override
    public void init() {
        String endpointName = getName();
        endpoint.setName(endpointName.substring(1, endpointName.length() - 1));
        endpoint.init();
    }

    @Override
    public void start() {
        endpoint.start();

        // Start timeout thread
        asyncTimeout = new AsyncTimeout();
        Thread timeoutThread = new Thread(asyncTimeout, getNameInternal() + "-AsyncTimeout");
        int priority = endpoint.getThreadPriority();
        if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
            priority = Thread.NORM_PRIORITY;
        }
        timeoutThread.setPriority(priority);
        timeoutThread.setDaemon(true);
        timeoutThread.start();
    }

    protected static class ConnectionHandler<S> implements AbstractEndpoint.Handler<S> {

        private final AbstractProtocol<S> proto;

        public ConnectionHandler(AbstractProtocol<S> proto) {
            this.proto = proto;
        }

        @Override
        public SocketState process(SocketWrapperBase<S> socket, SocketEvent status) {
            return null;
        }

        @Override
        public Object getGlobal() {
            return null;
        }

        @Override
        public Set<S> getOpenSockets() {
            return null;
        }

        @Override
        public void release(SocketWrapperBase<S> socketWrapper) {

        }

        @Override
        public void pause() {

        }

        @Override
        public void recycle() {

        }
    }

    protected class AsyncTimeout implements Runnable {

        private volatile boolean asyncTimeoutRunning = true;

        /**
         * The background thread that checks async requests and fires the
         * timeout if there has been no activity.
         */
        @Override
        public void run() {

            while (asyncTimeoutRunning) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // Ignore
                }
                long now = System.currentTimeMillis();
                for (Processor processor : waitingProcessors) {
                    processor.timeoutAsync(now);
                }

                // Loop if endpoint is paused
                while (endpoint.isPaused() && asyncTimeoutRunning) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // Ignore
                    }
                }
            }
        }


        protected void stop() {
            asyncTimeoutRunning = false;

            // Timeout any pending async request
            for (Processor processor : waitingProcessors) {
                processor.timeoutAsync(-1);
            }
        }
    }

}